package org.moonlight.jvm.rtda.share.heap.symref;

import com.google.gson.Gson;
import org.moonlight.jvm.classfile.constant.info.member.ConstantFieldRefInfo;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.classmember.Field;
import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool;

/**
 * 字段符号引用
 *
 * 在Java中，我们并不能在同一个类中定义名字相同，但类型不同的两个字段。
 * 其实，这只是Java语言的限制，而不是Java虚拟机规范的限制。
 * 站在Java虚拟机的角度，一个类是完全可以有多个同名字段的，只要它们的类型互不相同就可以。
 * 这也是为什么字段也需要描述符进行描述的原因
 *
 * @author Moonlight
 * @createTime 2023/8/30 8:40
 **/
public class FieldRef extends MemberRef {

    /** 字段信息 **/
    private Field field;

    public FieldRef(RtConstantPool rtCp, ConstantFieldRefInfo info) {
        super(rtCp, info);
    }

    /**
     * 通过解析 字段符号引用 得到 {@link Field} 字段信息
     *
     * @return Field {@link Field} 字段信息
     * @createTime 11:39 2023/8/30
     * @author moonlight
     **/
    public Field resolvedField() {
        if (this.field == null) {
            try {
                this.resolveFieldRef();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
        return this.field;
    }

    /**
     * 解析 字段符号引用
     *
     * 如果类D想通过字段符号引用访问类C的某个字段，首先要解析符号引用得到类C，然后根据字段名和描述符查找字段。
     * 如果字段查找失败，则虚拟机抛出NoSuchFieldError异常。
     * 如果查找成功，但D没有足够的权限访问该字段，则虚拟机抛出IllegalAccessError异常。
     *
     * @return Field {@link Field} 字段信息
     * @createTime 11:41 2023/8/30
     * @author moonlight
     **/
    private void resolveFieldRef() throws NoSuchFieldException {
        Class d = this.rtCp.getClazz();
        Class c = this.resolvedClass();

        Field field = this.lookupField(c, this.name, this.descriptor);
        if (field == null) {
            Gson gson = new Gson();
            throw new NoSuchFieldException("在Class: " + gson.toJson(c) + " 中没有找到字段: " + this.name + ", " + this.descriptor);
        }
        if (!field.isAccessibleTo(d)){
            throw new IllegalAccessError();
        }

        this.field = field;
    }

    /**
     * 查找字段.
     * 1. 如果字段是public，则任何类都可以访问。
     * 2. 如果字段是protected，则只有子类和同一个包下的类可以访问。
     * 3. 如果字段有默认访问权限（非public，非protected，也非privated），则只有同一个包下的类可以访问。
     * 4. 字段是private，只有声明这个字段的类才能访问。
     *
     * @param c {@link Class} 类对象
     * @param name 字段名
     * @param desc 字段描述符
     * @return Field 字段信息对象
     * @createTime 11:43 2023/8/30
     * @author moonlight
     **/
    private Field lookupField(Class c, String name, String desc) {
        if (c == null) {
            return null;
        }
        if (c.getFields() != null) {
            for (Field f : c.getFields()) {
                if (f != null && f.getName().equals(name) && f.getDescriptor().equals(desc)) {
                    return f;
                }
            }
        }
        if (c.getInterfaces() != null) {
            for (Class clazz : c.getInterfaces()) {
                if (clazz != null) {
                    Field f = lookupField(clazz, name, desc);
                    if (f != null) {
                        return f;
                    }
                }
            }
        }
        if (c.getSuperClass() != null) {
            return lookupField(c.getSuperClass(), name, desc);
        }
        return null;
    }

}
